/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  // also serves as the cursor for the index

  RecordId lastrid; // rid for the last one to scan
  BTreeIndex ind;  // The index


  RC     rc;
  RC     rci; // for the index
  int    key;
  string value;
  int    count;
  int    diff;

  // open the table file and open the index file
  rci = ind.open(table + ".idx", 'r');
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
  IndexCursor curs;
  curs.pid = 0;
  curs.eid = 0;

  // scan the table file from the beginning


  count = 0;
  int max = 0;
  int min = 0;
  int flag = 0;

  for (unsigned i = 0; i < cond.size(); i++) // setting the min and max bounds
  {
    if(cond[i].attr == 1) // condition on keys
    {
      if(cond[i].comp == SelCond::GT || cond[i].comp == SelCond::GE)
      {
        if(min == 0)
          min = atoi(cond[i].value);
        else
          if(min < atoi(cond[i].value))
            min = atoi(cond[i].value);
      }
      if(cond[i].comp == SelCond::LT || cond[i].comp == SelCond::LE)
      {
        if(max == 0)
          max = atoi(cond[i].value);
        else
          if(max > atoi(cond[i].value))
            max = atoi(cond[i].value);
      }
    }
    else // no conditions on keys
      flag++;
  }
  if (flag == cond.size()) // no conditions on
  {
    lastrid.pid = rf.endRid().pid;
    lastrid.sid = rf.endRid().sid;
    rid.pid = 0; //starts the way the table will
    rid.sid = 0; //slot id is 0 regardless
  }
  else
  {
    if((rc = ind.locate(max, curs)) >= 0)
    {
      ind.readForward(curs, key, lastrid); // not the last one
    }
    if((rc = ind.locate(min, curs)) >=0) // put the rid to the key succesful
    {
      rid.pid = curs.pid;
      rid.sid = curs.eid;
      key = min;
    }
  }

  while (rid != lastrid) {
    // read the tuple
    if(attr != 1)
      if ((rc = rf.read(rid, key, value)) < 0) {
	fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
	goto exit_select;
      }

    if(key == 0) //when key is 0, not a valid tuple
      goto next_tuple;

    // check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
        diff = key - atoi(cond[i].value);
        break;
      case 2:
        diff = strcmp(value.c_str(), cond[i].value);
        break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
        if (diff != 0) goto next_tuple;
        break;
      case SelCond::NE:
        if (diff == 0) goto next_tuple;
        break;
	
      case SelCond::GT:
        if (diff <= 0) goto next_tuple;
        break;
      case SelCond::LT:
        if (diff >= 0) goto next_tuple;
        break;
      case SelCond::GE:
        if (diff < 0) goto next_tuple;
        break;
      case SelCond::LE:
        if (diff > 0) goto next_tuple;
        break;
	
      }
    }

    // the condition is met for the tuple.
    // increase matching tuple counter

    count++;

    // print the tuple
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break;
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
    }

    // move to the next tuple
    next_tuple:
    if(flag == cond.size())
      rid++;
    else
      ind.readForward(curs, key, rid);
  }

  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count); //
  }
  rc = 0;

  // close the table file and return
  exit_select:
  ind.close();
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  BTreeIndex ind;
  RecordFile rf;
  RecordId rid;
  RecordId start; // the record id of the 1st table entry
  start.pid = start.sid = 0;

  int key; // where the line parser will store the key
  string value; // where the line parser will store the value
  RC rc;

  if(index)
    rc = ind.open(table + ".idx", 'w');
  rc = rf.open(table + ".tbl", 'w');

  string line;
  ifstream file (loadfile.c_str(), ifstream::in);
  if (file.is_open())
  {
    while (file.good())
    {
      getline(file, line);
      parseLoadLine(line, key, value);

      if (index)
	rc = ind.insert(key, rid, ind.rootPid, ind.rootPid,
			ind.treeHeight, false, 0);
      rc = rf.append(key, value, rid);
      if (rc < 0)
      {
	fprintf(stderr, "Error: failed to add to table %s \n", table.c_str());
	return rc;
      }
    }
  }
  else
  {
    fprintf(stderr, "Error: file %s failed to open \n", loadfile.c_str());
    return -1;
  }

  if(index)
    ind.close();
  rf.close();
  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;

    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');

    // if there is nothing left, set the value to empty string
    if (c == 0) {
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
